// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "entd/utils.h"

#include <dirent.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <wordexp.h>

#include <iostream>

#include <base/basictypes.h>
#include <base/file_util.h>
#include <base/logging.h>

namespace entd {

namespace utils {


void* malloc(size_t size) {
  void* rv = ::malloc(size);
  if (!rv)
    exit(2);

  return rv;
}

void free(void *buf) {
  ::free(buf);
}

// Extracts a C string from a V8 Utf8Value.
const char* ToCString(const v8::String::Utf8Value& value) {
  return *value ? *value : "<string conversion failed>";
}

bool CheckHostnameCharset(const std::string& str) {
  for (std::string::const_iterator i = str.begin(); i < str.end(); ++i) {
    if (!(isalnum(*i) || *i == '.' || *i == '-')) {
      return false;
    }
  }

  return true;
}

// Reads a file into a v8 string.
v8::Handle<v8::String> ReadFile(const std::string& name) {
  std::string filestr;
  v8::Handle<v8::String> result;
  if (file_util::ReadFileToString(FilePath(name), &filestr))
    result = v8::String::New(filestr.c_str(), filestr.size());
  return result;
}

std::vector<std::string> ReadDirectory(const std::string& path) {
  std::vector<std::string> dirnames;
  DIR *dirp = opendir(path.c_str());

  if (dirp == NULL) {
    LOG(WARNING) << "Unable to open directory: " << path;
    return dirnames;
  }

  while (1) {
    struct dirent *direntp = readdir(dirp);
    if (direntp == NULL)
      break;
    std::string dirname(direntp->d_name);
    if (dirname == "." || dirname == "..")
      continue;
    dirnames.push_back(dirname);
  }

  closedir(dirp);
  return dirnames;
}

std::string ExpandFilePath(const std::string& path) {
  std::string res;
  wordexp_t path_exp;

  wordexp(path.c_str(), &path_exp, 0);

  if (path_exp.we_wordc > 0) {
    res = std::string(path_exp.we_wordv[0]);
  } else {
    res = path;
  }

  wordfree(&path_exp);
  return res;
}

std::string ValueAsUtf8String(v8::Handle<v8::Value> value) {
  v8::Local<v8::String> s = value->ToString();
  return std::string(*v8::String::Utf8Value(s));
}

std::string GetPropertyAsString(v8::Handle<v8::Object> obj,
                                const std::string& name) {
  std::string res;
  v8::Handle<v8::String> jsname = v8::String::New(name.c_str());
  if (obj->Has(jsname)) {
    res = ValueAsUtf8String(obj->Get(jsname));
  }
  return res;
}

// "delim" only applies to object properties; it is ignored for Arrays
StringList GetPropertyAsStringList(v8::Handle<v8::Object> obj,
                                   const std::string& name,
                                   const std::string& delim) {
  StringList result;
  v8::Handle<v8::String> jsname = v8::String::New(name.c_str());

  if (obj->Has(jsname)) {
    v8::Local<v8::Value> jsvalue = obj->Get(jsname);

    if (jsvalue->IsArray()) {
      v8::Local<v8::Array> jsarray =
          v8::Local<v8::Array>::Cast(obj->Get(jsname));

      for (size_t i = 0; i < jsarray->Length(); i++) {
        std::string s = ValueAsUtf8String(jsarray->Get(i));
        result.push_back(s);
      }

    } else if (jsvalue->IsObject()) {
      v8::Local<v8::Object> paramobj =
          v8::Local<v8::Object>::Cast(obj->Get(jsname));

      v8::Local<v8::Array> props = paramobj->GetPropertyNames();
      for (size_t i = 0; i < props->Length(); ++i) {
        v8::Handle<v8::Value> propname = props->Get(i);
        std::string name = ValueAsUtf8String(propname);
        std::string value = ValueAsUtf8String(paramobj->Get(propname));
        std::string s = name + delim + value;
        result.push_back(s);
      }
    }
  }
  return result;
}

v8::Handle<v8::Value> ThrowV8Exception(const std::string& err) {
  ThrowException(v8::String::New(err.c_str()));
  return v8::Undefined();
}

void ReportV8Exception(v8::TryCatch* try_catch) {
  v8::HandleScope handle_scope;
  v8::String::Utf8Value exception(try_catch->Exception());
  const char* exception_string = utils::ToCString(exception);
  v8::Handle<v8::Message> message = try_catch->Message();

  if (message.IsEmpty()) {
    // V8 didn't provide any extra information about this error; just
    // print the exception.
    LOG(ERROR) << "V8 Exception: " << exception_string << '\n';

  } else {
    // Print (filename):(line number): (message).
    v8::String::Utf8Value filename(message->GetScriptResourceName());
    const char* filename_string = utils::ToCString(filename);
    int linenum = message->GetLineNumber();
    LOG(ERROR) << "V8 Exception: " << filename_string << ':' << linenum <<
        ": " << exception_string;

    // Print line of source code.
    v8::String::Utf8Value sourceline(message->GetSourceLine());
    const char* sourceline_string = utils::ToCString(sourceline);
    LOG(ERROR) << sourceline_string;
  }
}

// Note: This assumes it is not getting called from inside a JS
// stack (current use cases), so it users LOG(ERROR)
// instead of ThrowException
// If called from a JS stack, the caller should throw an
// exception if this returns an empty handle.
v8::Handle<v8::Value> CallV8Function(v8::Handle<v8::Object> obj,
                             const std::string& cb_name,
                             int argc, v8::Handle<v8::Value> argv[]) {
  if (obj.IsEmpty() || !obj->IsObject()) {
    LOG(ERROR) << "Invalid object when trying to call: " << cb_name;
    return v8::Handle<v8::Value>();
  }

  v8::Handle<v8::Value> cb = obj->Get(v8::String::New(cb_name.c_str()));
  if (cb->IsUndefined()) {
    LOG(ERROR) << "Object does not have property: "<< cb_name;
    return v8::Handle<v8::Value>();
  }

  if (!cb->IsFunction()) {
    LOG(ERROR) << "Property is not a function: " << cb_name;
    return v8::Handle<v8::Value>();
  }

  return v8::Handle<v8::Function>::Cast(cb)->Call(obj, argc, argv);
}

}  // namespace utils

}  // namespace entd
